﻿using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyModules
{
    class ConfigManager
    {
        private IniFile param;
        public IniFile Param { get { return this.param; } }

        public delegate string GetConfig();

        public delegate string Reader(object item);

        public delegate void EventBind(object item, bool remove = false);

        private readonly Dictionary<string, Reader> readers = new Dictionary<string, Reader>();
        public Dictionary<string, Reader> Readers { get { return this.readers; } }

        private readonly Dictionary<string, EventBind> binders = new Dictionary<string, EventBind>();
        public Dictionary<string, EventBind> Binders { get { return this.binders; } }

        private readonly Dictionary<object, string> bindings = new Dictionary<object, string>();
        public Dictionary<object, string> Bindings { get { return this.bindings; } }

        private readonly char seperator = '&';
        public char Seperator { get { return this.seperator; } }

        private GetConfig getConfig;


        private string readNumericUpDown(object item)
        {
            string error = null;
            string[] info = this.bindings[item].Split(this.seperator);
            string section = info[0];
            string key = info[1];
            string def = "";
            if(info.Length > 2)
            {
                def = info[2];
            }
            try
            {
                (item as NumericUpDown).Value =
                    Convert.ToDecimal(this.param.ReadString(section, key, def));
            }
            catch(Exception e)
            {
                error = e.Message.ToString();
            }
            return error;
        }


        private string readCheckBox(object item)
        {
            string error = null;
            string[] info = this.bindings[item].Split(this.seperator);
            string section = info[0];
            string key = info[1];
            int def = 1;
            try
            {
                def = Convert.ToInt32(info[2]);
            }
            catch(Exception e) { }
            try
            {
                (item as CheckBox).Checked =
                    Convert.ToBoolean(this.param.ReadInt(section, key, def));
            }
            catch (Exception e)
            {
                error = e.Message.ToString();
            }
            return error;
        }


        private string readComboBox(object item)
        {
            string error = null;
            string[] info = this.bindings[item].Split(this.seperator);
            string section = info[0];
            string key = info[1];
            int def = -1;
            try
            {
                def = Convert.ToInt32(info[2]);
            }
            catch (Exception e) { }
            try
            {
                (item as ComboBox).SelectedIndex =
                    this.param.ReadInt(section, key, def);
            }
            catch (Exception e)
            {
                error = e.Message.ToString();
            }
            return error;
        }


        private void bindNumericUpDown(object item, bool remove)
        {
            if (remove)
            {
                (item as NumericUpDown).ValueChanged -= this.NumericUpDownValueChange;
            }
            else
            {
                (item as NumericUpDown).ValueChanged += this.NumericUpDownValueChange;
            }
        }


        private void bindCheckBox(object item, bool remove)
        {
            if (remove)
            {
                (item as CheckBox).CheckedChanged -= this.CheckBoxCheckedChange;
            }
            else
            {
                (item as CheckBox).CheckedChanged += this.CheckBoxCheckedChange;
            }
        }


        private void bindComboBox(object item, bool remove)
        {
            if (remove)
            {
                (item as ComboBox).SelectedIndexChanged -= this.ComboBoxSelectedIndexChange;
            }
            else
            {
                (item as ComboBox).SelectedIndexChanged += this.ComboBoxSelectedIndexChange;
            }
        }


        private void NumericUpDownValueChange(object sender, EventArgs e)
        {
            string[] info = this.bindings[sender].Split(this.seperator);
            string section = info[0];
            string key = info[1];
            string value = (sender as NumericUpDown).Value.ToString();
            this.param.WriteString(section, key, value);
        }


        private void CheckBoxCheckedChange(object sender, EventArgs e)
        {
            string[] info = this.bindings[sender].Split(this.seperator);
            string section = info[0];
            string key = info[1];
            int value = (sender as CheckBox).Checked ? 1 : 0;
            this.param.WriteInt(section, key, value);
        }


        private void ComboBoxSelectedIndexChange(object sender, EventArgs e)
        {
            string[] info = this.bindings[sender].Split(this.seperator);
            string section = info[0];
            string key = info[1];
            int value = (sender as ComboBox).SelectedIndex;
            this.param.WriteInt(section, key, value);
        }


        public ConfigManager(IniFile param)
        {
            this.param = param;

            this.readers.Add(typeof(NumericUpDown).ToString(),new Reader(readNumericUpDown));
            this.binders.Add(typeof(NumericUpDown).ToString(), new EventBind(bindNumericUpDown));

            this.readers.Add(typeof(CheckBox).ToString(), new Reader(readCheckBox));
            this.binders.Add(typeof(CheckBox).ToString(), new EventBind(bindCheckBox));

            this.readers.Add(typeof(ComboBox).ToString(), new Reader(readComboBox));
            this.binders.Add(typeof(ComboBox).ToString(), new EventBind(bindComboBox));
        }


        public ConfigManager(GetConfig getConfig)
        {
            this.getConfig = getConfig;
            this.param = new IniFile(this.getConfig());

            this.readers.Add(typeof(NumericUpDown).ToString(), new Reader(readNumericUpDown));
            this.binders.Add(typeof(NumericUpDown).ToString(), new EventBind(bindNumericUpDown));

            this.readers.Add(typeof(CheckBox).ToString(), new Reader(readCheckBox));
            this.binders.Add(typeof(CheckBox).ToString(), new EventBind(bindCheckBox));

            this.readers.Add(typeof(ComboBox).ToString(), new Reader(readComboBox));
            this.binders.Add(typeof(ComboBox).ToString(), new EventBind(bindComboBox));
        }


        public bool Add(object item, string section , string key, object def = null)
        {
            if (this.bindings.ContainsKey(item))
            {
                return false;
            }
            string type = item.GetType().ToString();
            string info = section + this.seperator + key + this.seperator + def;
            this.bindings.Add(item, info);
            if (this.binders.ContainsKey(type)) {
                this.binders[type](item);
            }
            return true;
        }


        public bool Remove(object item)
        {
            if (!this.bindings.ContainsKey(item))
            {
                return false;
            }
            this.binders[item.GetType().ToString()](item, true);
            return this.bindings.Remove(item);
        }


        public IList<string> Init()
        {
            IList<string> errors = new List<string>();
            if (this.getConfig != null)
            {
                this.param = new IniFile(this.getConfig());
            }
            foreach (object item in this.bindings.Keys)
            {
                string error = Read(item);
                if (error != null)
                {
                    errors.Add(error);
                }
            }
            return errors;
        }


        public string Read(object item)
        {
            string type = item.GetType().ToString();
            if (!this.readers.ContainsKey(type))
            {
                return $"No reader for this type:[{type}]";
            }
            return this.readers[type](item);
        }


        public void AddReader(string type, Reader reader, bool overwrite = false)
        {
            if (!this.readers.ContainsKey(type))
            {
                this.readers.Add(type, reader);
            }
            else if (overwrite)
            {
                this.readers[type] = reader;
            }
            else
            {
                throw new Exception("Existed type. If you mean to override the existed one, set 'overwrite' true");
            }
        }

        public bool RemoveReader(string type)
        {
            if (!this.readers.ContainsKey(type))
            {
                return false;
            }
            return this.readers.Remove(type);
        }


        public void AddEventBind(string type, EventBind bind, bool overwrite = false)
        {
            if(!this.binders.ContainsKey(type))
            {
                this.binders.Add(type, bind);
            }
            else if (overwrite)
            {
                foreach(object item in this.bindings.Keys)
                {
                    if(item.GetType().ToString() == type)
                    {
                        this.binders[type](item, true);
                    }
                }
                this.binders[type] = bind;
            }
            else
            {
                throw new Exception("Existed type. If you mean to override the existed one, set 'overwrite' true");
            }
            foreach (object item in this.bindings.Keys)
            {
                if (item.GetType().ToString() == type)
                {
                    bind(item);
                }
            }
        }


        public bool RemoveEventBind(string type)
        {
            if (!this.binders.ContainsKey(type))
            {
                return false;
            }
            foreach (object item in this.bindings.Keys)
            {
                if (item.GetType().ToString() == type)
                {
                    this.binders[type](item, true);
                }
            }
            return this.binders.Remove(type);
        }
    }
}
